
#include "BackupFilesystem.h"
#include "BackupFSUtils.h"
#include "../logger/BackupFSLogger.h"
#include "BackupFSCore.h"
#include <sddl.h>
#include <iostream>
#include <mutex>
#include <sstream>
#include <unordered_map>

namespace BackupFS
{
	static const DWORD VOLUMESERIAL = 0x20241209;

	static NTSTATUS CreateMainStream(
		BackupFSFileNodes* fs_filenodes, const std::wstring& filename,
		const std::pair<std::wstring, std::wstring>& stream_names,
		DWORD file_attributes_and_flags,
		PDOKAN_IO_SECURITY_CONTEXT security_context)
	{
		// When creating a new a alternated stream, we need to be sure
		// the main stream exist otherwise we create it.
		auto MainStream_name = BackupFSUtils::GetFileNameStreamLess(filename, stream_names);
		if (!fs_filenodes->Find(MainStream_name))
		{
			if (BackupFSCore::GetInstance()->IsReadOnlyFS())
			{
				return STATUS_ACCESS_DENIED;
			}
			fs_filenodes->Add(std::make_shared<BackupFSFileNode>(MainStream_name, false,file_attributes_and_flags, security_context),{});
		}
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_CreateFile(LPCWSTR filename, PDOKAN_IO_SECURITY_CONTEXT security_context,
			ACCESS_MASK desiredaccess, ULONG fileattributes,
			ULONG /*shareaccess*/, ULONG createdisposition,
			ULONG createoptions, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		ACCESS_MASK generic_desiredaccess;
		DWORD creation_disposition;
		DWORD file_attributes_and_flags;

		DokanMapKernelToUserCreateFileFlags(
			desiredaccess, fileattributes, createoptions, createdisposition,
			&generic_desiredaccess, &file_attributes_and_flags,
			&creation_disposition);

		auto filename_str = std::wstring(filename);
		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		auto stream_names = BackupFSUtils::GetStreamNames(filename_str);
		
		// Windows will automatically try to create and access different system
		// directories.
		if (filename_str == L"\\System Volume Information" ||
			filename_str == L"\\$RECYCLE.BIN")
		{
			return STATUS_NO_SUCH_FILE;
		}

		if (f && f->IsDirectory)
		{
			if (createoptions & FILE_NON_DIRECTORY_FILE)
			{
				return STATUS_FILE_IS_A_DIRECTORY;
			}
			dokanfileinfo->IsDirectory = true;
		}

		// TODO Use AccessCheck to check security rights

		if (dokanfileinfo->IsDirectory)
		{
			if (creation_disposition == CREATE_NEW ||
				creation_disposition == OPEN_ALWAYS)
			{
				// Cannot create a stream as directory.
				if (!stream_names.second.empty())
				{
					return STATUS_NOT_A_DIRECTORY;
				}

				if (f)
				{
					return STATUS_OBJECT_NAME_COLLISION;
				}
				if (BackupFSCore::GetInstance()->IsReadOnlyFS())
				{
					return STATUS_ACCESS_DENIED;
				}
				auto newfileNode = std::make_shared<BackupFSFileNode>(filename_str, true, FILE_ATTRIBUTE_DIRECTORY, security_context);
				return filenodes->Add(newfileNode, stream_names);
			}

			if (f && !f->IsDirectory)
			{
				return STATUS_NOT_A_DIRECTORY;
			}
			if (!f)
			{
				return STATUS_OBJECT_NAME_NOT_FOUND;
			}
		}
		else
		{

			// Cannot overwrite an hidden or system file.
			if (f && (((!(file_attributes_and_flags & FILE_ATTRIBUTE_HIDDEN) &&
				(f->Attributes & FILE_ATTRIBUTE_HIDDEN)) ||
				(!(file_attributes_and_flags & FILE_ATTRIBUTE_SYSTEM) &&
					(f->Attributes & FILE_ATTRIBUTE_SYSTEM))) &&
				(creation_disposition == TRUNCATE_EXISTING ||
					creation_disposition == CREATE_ALWAYS)))
			{
				return STATUS_ACCESS_DENIED;
			}
			if ((file_attributes_and_flags & FILE_FLAG_DELETE_ON_CLOSE  ) &&  BackupFSCore::GetInstance()->IsReadOnlyFS())
			{
				return STATUS_ACCESS_DENIED;
			}
			// Cannot delete a file with readonly attributes.
			if ((f && (f->Attributes & FILE_ATTRIBUTE_READONLY) ||
				(file_attributes_and_flags & FILE_ATTRIBUTE_READONLY)) &&
				(file_attributes_and_flags & FILE_FLAG_DELETE_ON_CLOSE))
			{
				return STATUS_CANNOT_DELETE;
			}

			// Cannot open a readonly file for writing.
			if ((creation_disposition == OPEN_ALWAYS ||
				creation_disposition == OPEN_EXISTING) &&
				f && (f->Attributes & FILE_ATTRIBUTE_READONLY) &&
				desiredaccess & FILE_WRITE_DATA)
			{
				return STATUS_ACCESS_DENIED;
			}

			// Cannot overwrite an existing read only file.
			// FILE_SUPERSEDE can as it replace and not overwrite.
			if ((creation_disposition == CREATE_NEW ||
				(creation_disposition == CREATE_ALWAYS &&
					createdisposition != FILE_SUPERSEDE) ||
				creation_disposition == TRUNCATE_EXISTING) &&
				f && (f->Attributes & FILE_ATTRIBUTE_READONLY))
			{
				return STATUS_ACCESS_DENIED;
			}

			if (creation_disposition == CREATE_NEW ||
				creation_disposition == CREATE_ALWAYS ||
				creation_disposition == OPEN_ALWAYS ||
				creation_disposition == TRUNCATE_EXISTING) {
				// Combines the file attributes and flags specified by
				// dwFlagsAndAttributes with FILE_ATTRIBUTE_ARCHIVE.
				file_attributes_and_flags |= FILE_ATTRIBUTE_ARCHIVE;
				// We merge the attributes with the existing file attributes
				// except for FILE_SUPERSEDE.
				if (f && createdisposition != FILE_SUPERSEDE)
					file_attributes_and_flags |= f->Attributes;
				// Remove non specific attributes.
				file_attributes_and_flags &= ~FILE_ATTRIBUTE_STRICTLY_SEQUENTIAL;
				// FILE_ATTRIBUTE_NORMAL is override if any other attribute is set.
				file_attributes_and_flags &= ~FILE_ATTRIBUTE_NORMAL;
			}

			switch (creation_disposition)
			{
			case CREATE_ALWAYS:
			{
				/*
				 * Creates a new file, always.
				 *
				 * We handle FILE_SUPERSEDE here as it is converted to TRUNCATE_EXISTING
				 * by DokanMapKernelToUserCreateFileFlags.
				 */

				if (!stream_names.second.empty())
				{
					// The createfile is a alternate stream,
					// we need to be sure main stream exist
					auto n = CreateMainStream(filenodes, filename_str, stream_names,
						file_attributes_and_flags, security_context);
					if (n != STATUS_SUCCESS)
					{
						return n;
					}
				}
				if (BackupFSCore::GetInstance()->IsReadOnlyFS())
				{
					return STATUS_ACCESS_DENIED;
				}
				filenodes->Add(std::make_shared<BackupFSFileNode>(filename_str, false,file_attributes_and_flags,
					security_context),
					stream_names);
				/*
				 * If the specified file exists and is writable, the function overwrites
				 * the file, the function succeeds, and last-error code is set to
				 * ERROR_ALREADY_EXISTS
				 */
				if (f)
				{
					return STATUS_OBJECT_NAME_COLLISION;
				}
			} break;
			case CREATE_NEW:
			{
				/*
				 * Creates a new file, only if it does not already exist.
				 */
				if (f) return STATUS_OBJECT_NAME_COLLISION;

				if (BackupFSCore::GetInstance()->IsReadOnlyFS())
				{
					return STATUS_ACCESS_DENIED;
				}
				if (!stream_names.second.empty())
				{
					return STATUS_SUCCESS;
				}
				filenodes->Add(std::make_shared<BackupFSFileNode>(filename_str, false,
					file_attributes_and_flags,
					security_context),
					stream_names);
			}
			break;
			case OPEN_ALWAYS:
			{
				/*
				 * Opens a file, always.
				 */

				if (!f)
				{
					if (BackupFSCore::GetInstance()->IsReadOnlyFS())
					{
						return STATUS_ACCESS_DENIED;
					}
					filenodes->Add(std::make_shared<BackupFSFileNode>(filename_str, false, file_attributes_and_flags,security_context),	stream_names);
				}
			}
			break;
			case OPEN_EXISTING:
			{
				/*
				 * Opens a file or device, only if it exists.
				 * If the specified file or device does not exist, the function fails
				 * and the last-error code is set to ERROR_FILE_NOT_FOUND
				 */
				if (!f)
				{
					return STATUS_OBJECT_NAME_NOT_FOUND;
				}
			}
			break;
			default:
				break;
			}
		}

		/*
		 * CREATE_NEW && OPEN_ALWAYS
		 * If the specified file exists, the function fails and the last-error code is
		 * set to ERROR_FILE_EXISTS
		 */
		if (f && (creation_disposition == CREATE_NEW || creation_disposition == OPEN_ALWAYS))
		{
			return STATUS_OBJECT_NAME_COLLISION;
		}

		return STATUS_SUCCESS;
	}

	static void DOKAN_CALLBACK BackupFS_CleanUp(LPCWSTR filename,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return ;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);
		BackupFSUtils::RemoveStreamType(filename_str);
		if (dokanfileinfo->DeleteOnClose) 
		{
			// Delete happens during cleanup and not in close event.
			filenodes->Remove(filename_str);
		}
		auto f = filenodes->Find(filename);
		if (f)
		{
			f->Cleanup();
		}
	}

	static void DOKAN_CALLBACK BackupFS_CloseFile(LPCWSTR filename,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);
		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		if (f)
		{
			f->Close(dokanfileinfo->ProcessId);
		}
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_ReadFile(LPCWSTR filename, LPVOID buffer,
		DWORD bufferlength,
		LPDWORD readlength,
		LONGLONG offset,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		
		*readlength = f->Read(buffer, bufferlength, offset);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_WriteFile(LPCWSTR filename, LPCVOID buffer,
		DWORD number_of_bytes_to_write,
		LPDWORD number_of_bytes_written,
		LONGLONG offset,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		if (f->IsLockedOffset(offset, dokanfileinfo->ProcessId))
		{
			return STATUS_LOCK_NOT_GRANTED;
		}

		auto file_size = f->GetFileSize();

		// An Offset -1 is like the file was opened with FILE_APPEND_DATA
		// and we need to write at the end of the file.
		if (offset == -1) offset = file_size;

		*number_of_bytes_written = f->Write(buffer, number_of_bytes_to_write, offset);

		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_FlushFileBuffers(LPCWSTR filename, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		// Nothing to flush, we directly write the content into our buffer.

		if (f->MainStream)
		{
			f = f->MainStream;
		}
		f->Times.LastAccess = f->Times.LastWrite = FileTimes::GetCurrentTime();

		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_GetFileInformation(LPCWSTR filename, LPBY_HANDLE_FILE_INFORMATION buffer,
			PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		if (!f)
		{
			return STATUS_OBJECT_NAME_NOT_FOUND;
		}
		buffer->dwFileAttributes = f->Attributes;
		BackupFSUtils::LlongToFileTime(f->Times.Creation, buffer->ftCreationTime);
		BackupFSUtils::LlongToFileTime(f->Times.LastAccess, buffer->ftLastAccessTime);
		BackupFSUtils::LlongToFileTime(f->Times.LastWrite, buffer->ftLastWriteTime);
		auto strLength = f->GetFileSize();
		BackupFSUtils::LlongToDwLowHigh(strLength, buffer->nFileSizeLow,
			buffer->nFileSizeHigh);
		BackupFSUtils::LlongToDwLowHigh(f->FileIndex, buffer->nFileIndexLow,
			buffer->nFileIndexHigh);
		// We do not track the number of links to the file so we return a fake value.
		buffer->nNumberOfLinks = 1;
		buffer->dwVolumeSerialNumber = VOLUMESERIAL;
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_FindFiles(LPCWSTR filename,
		PFillFindData fill_finddata,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto files = filenodes->ListFolder(filename_str);
		WIN32_FIND_DATAW findData;
		ZeroMemory(&findData, sizeof(WIN32_FIND_DATAW));
		for (const auto& f : files)
		{
			if (f->MainStream)
			{
				continue; // Do not list File Streams
			}
			const auto fileNodeName = BackupFSUtils::GetFileName(f->GetFileName());
			std::copy(fileNodeName.begin(), fileNodeName.end(),
				std::begin(findData.cFileName));
			findData.cFileName[fileNodeName.length()] = '\0';
			findData.dwFileAttributes = f->Attributes;
			BackupFSUtils::LlongToFileTime(f->Times.Creation, findData.ftCreationTime);
			BackupFSUtils::LlongToFileTime(f->Times.LastAccess,
				findData.ftLastAccessTime);
			BackupFSUtils::LlongToFileTime(f->Times.LastWrite, findData.ftLastWriteTime);
			auto file_size = f->GetFileSize();
			BackupFSUtils::LlongToDwLowHigh(file_size, findData.nFileSizeLow,
				findData.nFileSizeHigh);
			fill_finddata(&findData, dokanfileinfo);
		}
		return STATUS_SUCCESS;
	}
	
	static NTSTATUS DOKAN_CALLBACK BackupFS_SetFileAttributes(
		LPCWSTR filename, DWORD fileattributes, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;

		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		if (!f) return STATUS_OBJECT_NAME_NOT_FOUND;

		// from https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfileattributesw
		DWORD const attributes_allowed_to_set =
			FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NORMAL |
			FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | FILE_ATTRIBUTE_OFFLINE |
			FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM |
			FILE_ATTRIBUTE_TEMPORARY;

		fileattributes &= attributes_allowed_to_set;

		DWORD new_file_attributes =
			(f->Attributes & ~attributes_allowed_to_set) | fileattributes;

		// FILE_ATTRIBUTE_NORMAL is overriden if any other attribute is set
		if ((new_file_attributes & FILE_ATTRIBUTE_NORMAL) &&
			(new_file_attributes & ~static_cast<DWORD>(FILE_ATTRIBUTE_NORMAL)))
			new_file_attributes &= ~static_cast<DWORD>(FILE_ATTRIBUTE_NORMAL);

		f->Attributes = new_file_attributes;
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_SetFileTime(LPCWSTR filename, CONST FILETIME* creationtime,
			CONST FILETIME* lastaccesstime, CONST FILETIME* lastwritetime,
			PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);
		if (!f)
		{
			return STATUS_OBJECT_NAME_NOT_FOUND;
		}
		FileTimes ftimes = { 0 };
		if (creationtime && !FileTimes::Empty(creationtime))
		{
			ftimes.Creation = BackupFSUtils::FileTimeToLlong(*creationtime);
		}
		if (lastaccesstime && !FileTimes::Empty(lastaccesstime))
		{
			ftimes.LastAccess = BackupFSUtils::FileTimeToLlong(*lastaccesstime);
		}
		if (lastwritetime && !FileTimes::Empty(lastwritetime))
		{
			ftimes.LastWrite = BackupFSUtils::FileTimeToLlong(*lastwritetime);
		}
		f->SetFileTime(ftimes.Creation, ftimes.LastWrite, ftimes.LastAccess);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_DeleteFile(LPCWSTR filename, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);

		if (!f) return STATUS_OBJECT_NAME_NOT_FOUND;

		if (f->IsDirectory) return STATUS_ACCESS_DENIED;
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_DeleteDirectory(LPCWSTR filename, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto files = filenodes->ListFolder(filename_str);
		if (files.size())
		{
			return STATUS_DIRECTORY_NOT_EMPTY;
		}
		//remove the folder
		auto file = filenodes->Find(filename_str);
		filenodes->Remove(file);

		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_MoveFile(LPCWSTR filename,
		LPCWSTR new_filename,
		BOOL replace_if_existing,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);
		auto new_filename_str = std::wstring(new_filename);
		BackupFSUtils::RemoveStreamType(new_filename_str);
		auto new_stream_names = BackupFSUtils::GetStreamNames(new_filename_str);
		if (new_stream_names.first.empty())
		{
			// new_filename is a stream name :<stream name>:<stream type>
			// We removed the stream type and now need to concat the filename and the
			// new stream name
			auto stream_names = BackupFSUtils::GetStreamNames(filename_str);
			new_filename_str =
				BackupFSUtils::GetFileNameStreamLess(filename, stream_names) +
				L":" + new_stream_names.second;
		}
		return filenodes->Move(filename_str, new_filename_str, replace_if_existing);
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_SetEndOfFile(
		LPCWSTR filename, LONGLONG ByteOffset, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);

		if (!f) return STATUS_OBJECT_NAME_NOT_FOUND;
		f->SetEndOfFile(ByteOffset);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_SetAllocationSize(
		LPCWSTR filename, LONGLONG alloc_size, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);

		if (!f)
		{
			return STATUS_OBJECT_NAME_NOT_FOUND;
		}
		f->SetEndOfFile(alloc_size);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_LockFile(LPCWSTR filename,
		LONGLONG byte_offset,
		LONGLONG length,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto filenodes = GET_FS_NODES;
		auto f = filenodes->Find(filename_str);

		f->LockRegion(byte_offset, length, dokanfileinfo->ProcessId);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_UnLockFile(LPCWSTR filename, LONGLONG byte_offset, LONGLONG length,
			PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto filenodes = GET_FS_NODES;
		auto f = filenodes->Find(filename_str);

		f->UnLockRegion(byte_offset, length, dokanfileinfo->ProcessId);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_GetDiskFreeSpace(
		PULONGLONG free_bytes_available, PULONGLONG total_number_of_bytes,
		PULONGLONG total_number_of_free_bytes, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunction;
		auto fs = GET_FS_INSTANCE;
		*free_bytes_available = fs->GetFreeSize();
		*total_number_of_bytes = fs->GetFSSize();
		*total_number_of_free_bytes = fs->GetFreeSize();
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_GetVolumeInformation(
		LPWSTR volumename_buffer, DWORD volumename_size,
		LPDWORD volume_serialnumber, LPDWORD maximum_component_length,
		LPDWORD filesystem_flags, LPWSTR filesystem_name_buffer,
		DWORD filesystem_name_size, PDOKAN_FILE_INFO /*dokanfileinfo*/)
	{
		DebugLogFunction;
		wcscpy_s(volumename_buffer, volumename_size, L"Backup File System");
		*volume_serialnumber = VOLUMESERIAL;
		*maximum_component_length = 255;
		*filesystem_flags = FILE_CASE_SENSITIVE_SEARCH | FILE_CASE_PRESERVED_NAMES |
			FILE_SUPPORTS_REMOTE_STORAGE | FILE_UNICODE_ON_DISK |
			FILE_NAMED_STREAMS;

		wcscpy_s(filesystem_name_buffer, filesystem_name_size, L"BackupFS");
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_Mounted(LPCWSTR MountPoint, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunction;
		(reinterpret_cast<BackupVFS*>(dokanfileinfo->DokanOptions->GlobalContext))
			->SetMountPoint(MountPoint);
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_UnMounted(PDOKAN_FILE_INFO /*dokanfileinfo*/)
	{
		DebugLogFunction;
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_GetFileSecurity(
		LPCWSTR filename, PSECURITY_INFORMATION security_information,
		PSECURITY_DESCRIPTOR security_descriptor, ULONG bufferlength,
		PULONG length_needed, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);		

		std::shared_lock lockFile(f->Security);

		// This will make dokan library return a default security descriptor
		if (!f->Security.Descriptor)
		{
			return STATUS_NOT_IMPLEMENTED;
		}

		// We have a Security Descriptor but we need to extract only informations
		// requested 1 - Convert the Security Descriptor to SDDL string with the
		// informations requested
		LPTSTR pStringBuffer = nullptr;
		if (!ConvertSecurityDescriptorToStringSecurityDescriptor(
			f->Security.Descriptor.get(), SDDL_REVISION_1, *security_information,
			&pStringBuffer, nullptr))
		{
			return STATUS_NOT_IMPLEMENTED;
		}

		// 2 - Convert the SDDL string back to Security Descriptor
		PSECURITY_DESCRIPTOR SecurityDescriptorTmp = nullptr;
		ULONG Size = 0;
		if (!ConvertStringSecurityDescriptorToSecurityDescriptor(
			pStringBuffer, SDDL_REVISION_1, &SecurityDescriptorTmp, &Size))
		{
			LocalFree(pStringBuffer);
			return STATUS_NOT_IMPLEMENTED;
		}
		LocalFree(pStringBuffer);

		*length_needed = Size;
		if (Size > bufferlength)
		{
			LocalFree(SecurityDescriptorTmp);
			return STATUS_BUFFER_OVERFLOW;
		}

		// 3 - Copy the new SecurityDescriptor to destination
		memcpy(security_descriptor, SecurityDescriptorTmp, Size);
		LocalFree(SecurityDescriptorTmp);

		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK BackupFS_SetFileSecurity(
		LPCWSTR filename, PSECURITY_INFORMATION security_information,
		PSECURITY_DESCRIPTOR security_descriptor, ULONG /*bufferlength*/,
		PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		if (BackupFSCore::GetInstance()->IsReadOnlyFS())
		{
			return STATUS_ACCESS_DENIED;
		}
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		static GENERIC_MAPPING generic_mapping = { FILE_GENERIC_READ, FILE_GENERIC_WRITE,
												FILE_GENERIC_EXECUTE,
												FILE_ALL_ACCESS };
		auto f = filenodes->Find(filename_str);

		std::unique_lock securityLock(f->Security);
		//f->Security.SetDescriptor(security_descriptor);
		f->SetSecurity(security_descriptor);
		
		return STATUS_SUCCESS;
	}

	static NTSTATUS DOKAN_CALLBACK
		BackupFS_FindStreams(LPCWSTR filename, PFillFindStreamData fill_findstreamdata,
			PVOID findstreamcontext, PDOKAN_FILE_INFO dokanfileinfo)
	{
		DebugLogFunctionWithFileName;
		auto filenodes = GET_FS_NODES;
		auto filename_str = std::wstring(filename);

		BackupFSUtils::RemoveStreamType(filename_str);
		auto f = filenodes->Find(filename_str);

		if (!f)
		{
			return STATUS_OBJECT_NAME_NOT_FOUND;
		}

		auto streams = f->GetStreams();

		WIN32_FIND_STREAM_DATA stream_data;
		ZeroMemory(&stream_data, sizeof(WIN32_FIND_STREAM_DATA));

		if (!f->IsDirectory)
		{
			// Add the main stream name - \foo::$DATA by returning ::$DATA
			std::copy(BackupFSUtils::DataStreamNameStr.begin(),
				BackupFSUtils::DataStreamNameStr.end(),
				std::begin(stream_data.cStreamName) + 1);
			stream_data.cStreamName[0] = ':';
			stream_data.cStreamName[BackupFSUtils::DataStreamNameStr.length() + 1] =
				L'\0';
			stream_data.StreamSize.QuadPart = f->GetFileSize();
			if (!fill_findstreamdata(&stream_data, findstreamcontext))
			{
				return STATUS_BUFFER_OVERFLOW;
			}
		}
		else if (streams.empty())
		{
			// The node is a directory without any alternate streams
			return STATUS_END_OF_FILE;
		}

		// Add the alternated stream attached
		// for \foo:bar we need to return in the form of bar:$DATA
		for (const auto& stream : streams)
		{
			auto stream_names = BackupFSUtils::GetStreamNames(stream.first);
			if (stream_names.second.length() +
				BackupFSUtils::DataStreamNameStr.length() + 1 >
				sizeof(stream_data.cStreamName))
				continue;
			// Copy the filename foo
			std::copy(stream_names.second.begin(), stream_names.second.end(),
				std::begin(stream_data.cStreamName) + 1);
			// Concat :$DATA
			std::copy(BackupFSUtils::DataStreamNameStr.begin(),
				BackupFSUtils::DataStreamNameStr.end(),
				std::begin(stream_data.cStreamName) +
				stream_names.second.length() + 1);
			stream_data.cStreamName[0] = ':';
			stream_data.cStreamName[stream_names.second.length() +
				BackupFSUtils::DataStreamNameStr.length() + 1] =
				L'\0';
			stream_data.StreamSize.QuadPart = stream.second->GetFileSize();
			if (!fill_findstreamdata(&stream_data, findstreamcontext))
			{
				return STATUS_BUFFER_OVERFLOW;
			}
		}
		return STATUS_SUCCESS;
	}

	DOKAN_OPERATIONS BackupFSOperations =
	{
		BackupFS_CreateFile,
		BackupFS_CleanUp,
		BackupFS_CloseFile,
		BackupFS_ReadFile,
		BackupFS_WriteFile,
		BackupFS_FlushFileBuffers,
		BackupFS_GetFileInformation,
		BackupFS_FindFiles,
		nullptr,//BackupFS_FindFilesWithPattern,
		BackupFS_SetFileAttributes,
		BackupFS_SetFileTime,
		BackupFS_DeleteFile,
		BackupFS_DeleteDirectory,
		BackupFS_MoveFile,
		BackupFS_SetEndOfFile,
		BackupFS_SetAllocationSize,
		BackupFS_LockFile,
		BackupFS_UnLockFile,
		BackupFS_GetDiskFreeSpace,
		BackupFS_GetVolumeInformation,
		BackupFS_Mounted,
		BackupFS_UnMounted,
		BackupFS_GetFileSecurity,
		BackupFS_SetFileSecurity,
		BackupFS_FindStreams
	};

}  // namespace BackupFS
